|     java thinking in java enum table driven   |    

Enum的一些基本性质

枚举型都是java.lang.Enum的派生类

Oracle官方手册上对Enum类的定义:

Class Enum<E extends Enum<E>>

先不管这个自限定结构的意义到底是什么,很明确的一点是:所有我们定义的枚举型都是Enum类的派生类。 下面的语句,声明并定义了一个叫Signal的枚举型。

public enum Signal { GREEN, YELLOW, RED,}

关于上面的Signal枚举型,记住两个事实:

枚举型可以有自己的成员

为什么说枚举型是一个是实实在在的类? 枚举型和普通类一样,可以有自己的字段和方法,甚至是自己的构造方法。

public enum Signal {
	GREEN, YELLOW, RED;

	private Signal(String des){description=des;}
	private String description;
	public String getDescription(){return description;}
	public String toString(){return this.name()+":	"+description;}
}

有两个地方需要注意:

  1. 最后一个枚举实例后面要用“分号”和下面的字段,方法隔开。
  2. 构造函数不能是public或protected的。而且只能在枚举型内部被调用。(实际相当于只能是private的)

枚举型的实质是“单例模式”的变种

Signal的例子中,GREEN,YELLOW,RED的真实身份是Signal类型的“静态实例”

关于Java的Enum型,必须记住Joshua Bloch的一句话:

简单讲就是:枚举型中的每个枚举都是它的一个静态成员字段。而且无法改变(常量)。

所以枚举型和单例模式本质上非常相似。他们都严格限制自己的对象实例的数量。不太严格地说,单例器其实是枚举型的一个特例:只有单个枚举实例。记住Joshua Bloch的另一句话:

以下代码为枚举型Signal的javap反编译结果:

public final class com.ciaoshen.thinkinjava.chapter19.Signal extends java.lang.Enum<com.ciaoshen.thinkinjava.chapter19.Signal> {
  public static final com.ciaoshen.thinkinjava.chapter19.Signal GREEN;
  public static final com.ciaoshen.thinkinjava.chapter19.Signal YELLOW;
  public static final com.ciaoshen.thinkinjava.chapter19.Signal RED;
  public static com.ciaoshen.thinkinjava.chapter19.Signal[] values();
  public static com.ciaoshen.thinkinjava.chapter19.Signal valueOf(java.lang.String);
  static {};
}

已经确定了GREEN,YELLOW,RED三个枚举都是静态常量成员字段。考虑到枚举型的构造函数无法在枚举型外部被使用,所以保证每个枚举是“effective final”。

另外看到编译器擅自生成了另外两个静态方法:values()和valueOf()。这两个是非常常用的用来获取枚举实例的手段:

注意!因为是编译器自动合成的,所以这两个方法在Enum里是找不到的。

而且代码中明确了enum Signal继承自Enum。注意泛型就是Signal自身。所以这也符合最开始Enum的自限定定义:

Class Enum<E extends Enum<E>>

而且Signal枚举型本身也是被final关键字修饰的,所以它无法被继承。而且由于它继承自Enum它也不能再继承其他类。

枚举类内部还可以定义抽象方法

枚举型内部允许定义抽象方法。如果这样做,这个抽象方法必须在每个枚举中被实现。我们对之前的Signal类做如下改造:

public enum Light {
    GREEN{public void show(){System.out.println("Green");}},
    YELLOW{public void show(){System.out.println("Yellow");}},
    RED{public void show(){System.out.println("Red");}};

    public abstract void show();
}

再用javap进行反编译,结果如下:

public abstract class com.ciaoshen.thinkinjava.chapter19.Signal extends java.lang.Enum<com.ciaoshen.thinkinjava.chapter19.Signal> {
  public static final com.ciaoshen.thinkinjava.chapter19.Signal GREEN;
  public static final com.ciaoshen.thinkinjava.chapter19.Signal YELLOW;
  public static final com.ciaoshen.thinkinjava.chapter19.Signal RED;
  public static com.ciaoshen.thinkinjava.chapter19.Signal[] values();
  public static com.ciaoshen.thinkinjava.chapter19.Signal valueOf(java.lang.String);
  public abstract void show();
  com.ciaoshen.thinkinjava.chapter19.Signal(java.lang.String, int, com.ciaoshen.thinkinjava.chapter19.Signal$1);
  static {};
}

class Signal前多了一个abstract修饰词。注意Signal$1其实代表了一个匿名内部类。

扒光枚举类的全部语法糖

怎么看都觉得枚举型还是有问题。到底内部是怎么实现的呢?让我们把所有语法糖都扒掉看一下(用dj反编译)。

下面两个反编译的例子来自《Java语法糖–枚举》。感谢作者:Rajoy。

第一个例子是最朴素的枚举型:

public enum Sex {  
         MALE,  
         FEMALE  
}

下面是简单javap反编译的结果:

public final class Sex extends java.lang.Enum{  
    public static final Sex MALE;  
    public static final Sex FEMALE;  
    public static Sex[] values();  
    public static Sex valueOf(java.lang.String)  
    static {};  
}

下面是用dj深度反编译的结果:

public final class Sex extends Enum  
{  

    public static Sex[] values()  
    {  
        return (Sex[])$VALUES.clone();  
    }  

    public static Sex valueOf(String s)  
    {  
        return (Sex)Enum.valueOf(Sex, s);  
    }  

    private Sex(String s, int i)  
    {  
        super(s, i);  
    }  

    public static final Sex MALE;  
    public static final Sex FEMALE;  
    private static final Sex $VALUES[];  

    static  
    {  
        MALE = new Sex("MALE", 0);  
        FEMALE = new Sex("FEMALE", 1);  
        $VALUES = (new Sex[] {  
            MALE, FEMALE  
        });  
    }  
}  

看清楚编译器背着我们做了什么了吗?

MALE,FEMALE枚举确实是静态常量。在最后的静态块里初始化。构造函数默认两个参数:第一个是String字面量,和变量名保持一致。第二个是声明时候的顺序id。Enum自带的ordinal()和compareTo()函数就以这个字段为依据。$VALUES字段还是字面量的数组。

再来一个复杂一点的例子:

public enum Sex {  
         MALE {  
                   public String toString() {  
                            return "我是男人";  
                   }  
         },  
         FEMALE {  
                   public String toString() {  
                            return "我是女人";  
                   }  
         };  
}

下面直接是dj反编译的结果:

public class Sex extends Enum  
{  

    public static Sex[] values()  
    {  
        return (Sex[])$VALUES.clone();  
    }  

    public static Sex valueOf(String s)  
    {  
        return (Sex)Enum.valueOf(Sex, s);  
    }  

    private Sex(String s, int i)  
    {  
        super(s, i);  
    }  

    public static void main(String args[])  
    {  
    }  


    public static final Sex MALE;  
    public static final Sex FEMALE;  
    private static final Sex $VALUES[];  

    static  
    {  
        MALE = new Sex("MALE", 0) {  

            public String toString()  
            {  
                return "\u6211\u662F\u7537\u4EBA";  
            }  

        }  
;  
        FEMALE = new Sex("FEMALE", 1) {  

            public String toString()  
            {  
                return "\u6211\u662F\u5973\u4EBA";  
            }  

        }  
;  
        $VALUES = (new Sex[] {  
            MALE, FEMALE  
        });  
    }  
}

这回重写了toString()方法。可以看到在Sex类内部是以匿名内部类的方式重新定义了Sex类的两个新派生类。

同样的,如果我们像之前那样先定义一个抽象方法,然后在枚举实例中实现它们的话,Sex类就会被定义成抽象类,并以匿名内部类的形式继承抽象类并实现抽象方法。

至此,java枚举型的语法糖浮云总算拨云见日。底层的逻辑总算自洽了。之前提到各种枚举型的特性也都都能得到很好的解释。

利用反射获取enum实例

下面是一个给定一个enum型,随机返回它enum实例的工具:

public class Enums{
    private static Random rand=new Random();

    public static <T extends Enum<T>> T random(Class<T> c ){
        return c.getEnumConstants()[rand.nextInt(c.getEnumConstants().length)];
    }
}

这里的关键就在于,利用了反射Class#getEnumConstants()方法获取enum实例。这里<T extends Enum<T>>代表对enum型泛型。

枚举型作为成员字段是“静态的”

看下面这个简单场景,枚举型Light是A类中的一个成员字段。

public class A{
	public enum Light {GREEN, YELLOW, RED}
}

但要访问Light枚举时,必须这么调用:

System.out.println(A.Light);

因为,在一个类内部的枚举型“隐式”地是静态的。用static修饰过。所以枚举型永远是静态成员变量。这样的“语法糖”我非常不喜欢。我宁愿光明正大地声明成static。

EnumSet

EnumSet的实现很值得一说。作者是Joshua Bloch。首先,EnumSet是抽象类。和大多数类库自带Set一样,它继承自AbstractSet,获得了AbstractSet和AbstractMap的部分功能。

实现它的两个具体类型:一个RegularEnumSet,一个JumboEnumSet。但之所以我们在代码中很少见到直接调用这两个类的构造函数,是因为主要由EnumSet的构造函数负责调用它们。通过noneOf()函数中的如下部分代码,基本可以判断:RegularEnumSet用在枚举规模小于64的情况下。而JamboEnumSet用在大于64的情况下。要做这样的拆分,估计还是出于效率的考虑。毕竟可扩展大小的开销不小。

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

和容器内部大多维护的是数组不同,在RegularEnumSet中维护的主力数据结构确实如书中所说是一个long型

private long elements = 0L;

然后看它的add()函数:明显是把long型64 bit中,某个枚举实例的次序属性(ordinal)所对应的位置设为1。用1 bit表示这个枚举实例的存在。所以也确实像书里说的,long型中的每一位,映射到一个具体的枚举实例。

    public boolean add(E e) {
        typeCheck(e);

        long oldElements = elements;
        elements |= (1L << ((Enum)e).ordinal());
        return elements != oldElements;
    }

至于JumboEnumSet,其中维护的主力容器是一个long型数组。看看它的构造器:

class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
	private static final long serialVersionUID = 334349849919042784L;

	private long elements[];

    private int size = 0;

    JumboEnumSet(Class<E>elementType, Enum[] universe) {
        super(elementType, universe);
        elements = new long[(universe.length + 63) >>> 6];
    }
	//... ...

相当于枚举规模对64取模获得的数组长度。

EnumSet的“效率”是值得信赖的。

EnumMap

EnumMap和EnumSet类似。但除了Enum型作为Key值,它还可以有一个Value值。因此使用更灵活。这里要推荐一个很实用的“命令模式”

所谓“命令模式”很简单,顾名思义,就是把“命令”当做Key值,然后相应的“行动”当做Value值,存储到EnumMap中。

由于前面说过每个枚举实例都可以重载一遍枚举类中的成员方法。甚至初始的成员方法可以是个抽象方法。前面也展示过,Enum语法糖剥去以后,每个枚举实例都是静态常量。重写成员方法,实际以匿名内部类的方法实现。这里命令模式中代表Value值的“行动”代码也可以用匿名内部类来实现。

interface Command { void action(); }

enum AlarmPoints {BATHROOM,KITCHEN}

public class EnumMaps {
	public static void main(String[] args) {
		EnumMap<AlarmPoints,Command> em = new EnumMap<AlarmPoints,Command>(AlarmPoints.class);

		em.put(KITCHEN, new Command() {
			public void action() { print("Kitchen fire!"); }
		});
		em.put(BATHROOM, new Command() {
			public void action() { print("Bathroom alert!"); }
		});
	}
}

用书上的一个例子。首先定义一个简单Command接口,只有action()一个方法。 我们的枚举类是AlarmPoints。有两个枚举实例:BATHROOM和KITCHEN。他们作为EnumMap的两个键值,对应了两个不同的action()方法。其中action()方法是以匿名内部类的形式被定义的。

每个枚举实例有自己特有的行为方法,是实际开发中非常常见的一种抽象。这种场景下,用EnumMap能使代码很清晰,简洁。

职责链(Chain of Responsibility)和状态机

职责链(Chain of Responsibility)是很常用的一种抽象。它的本质是把一系列的“操作”抽象成解决问题的一系列方法。在遇到问题之后,进行一一尝试,直到问题被解决为止,或者最终被标记为不可解决。

很明显,枚举很适合用来罗列一系列有限的解决方法。

class Problem{
	//some code here
}

public enum Solution{
	METHOD_A{
		public boolean handle(Problem p){
			//do something
		}
	}
	METHOD_B{
		public boolean handle(Problem p){
			//do something
		}
	}
	METHOD_C{
		public boolean handle(Problem p){
			//do something
		}
	}
	public abstract boolean handle();
}

状态机模拟的场景更复杂一些。但基本思想是不变的,所谓有限状态机,枚举型可以被用来罗列有限的状态。然后同样的,以匿名内部类的形式定义各个状态下的具体操作。而还可以根据一系列枚举的条件,来决定执行什么操作。练习10和练习11中的“售货机”模型就是有限状态机最好的例子。

多路分发

问题

“多路分发(Multiple Dispatching)”模式源自于多对象交互的场景。比如下面这个算数的例子。

interface Number{
	public Number plus(Number n);
	public Number multiple(Number n);
}
class Integer extends Number{}
class Real extends Number{}
class Rational extends Number{}

Number接口面向其他Number定义了加法和乘法。而Number底下有自然数,实数,有理数这样的派生类。所以当我们用两个数做加法或乘法的时候会像下面这样:

Number a=new Integer();
Number b=new Real();
a.plus(b);

但这里的问题是Java只支持单路分发,编译器只能对一个对象实施动态绑定。所以a.plus(b);是无法编译的。

解决方案

解决的方法就是“两路分发”。书中举了一个“石头剪刀布”游戏的例子。像下面这样的朴素方式是不行的:

interface Item {
	Outcome eval(Paper p);
	Outcome eval(Scissors s);
	Outcome eval(Rock r);
}

class Paper implements Item {
	public Outcome eval(Paper p) { return DRAW; }
	public Outcome eval(Scissors s) { return WIN; }
	public Outcome eval(Rock r) { return LOSE; }
}
class Scissors implements Item {
	public Outcome eval(Paper p) { return LOSE; }
	public Outcome eval(Scissors s) { return DRAW; }
	public Outcome eval(Rock r) { return WIN; }
}
class Rock implements Item {
	public Outcome eval(Paper p) { return WIN; }
	public Outcome eval(Scissors s) { return LOSE; }
	public Outcome eval(Rock r) { return DRAW; }
}

因为我们最终势必会产生Item.eval(Item it)这样的代码。这是无法编译的。解决的办法就是创建一个额外的面向Item(以Item型为参数)的方法。当系统动态绑定it对象的运行时类型后,再通过it对象回调this对象。

interface Item {
	Outcome compete(Item it);
	Outcome eval(Paper p);
	Outcome eval(Scissors s);
	Outcome eval(Rock r);
}

class Paper implements Item {
	public Outcome compete(Item it) { return it.eval(this); }
	public Outcome eval(Paper p) { return DRAW; }
	public Outcome eval(Scissors s) { return WIN; }
	public Outcome eval(Rock r) { return LOSE; }
}
class Scissors implements Item {
	public Outcome compete(Item it) { return it.eval(this); }
	public Outcome eval(Paper p) { return LOSE; }
	public Outcome eval(Scissors s) { return DRAW; }
	public Outcome eval(Rock r) { return WIN; }
}
class Rock implements Item {
	public Outcome compete(Item it) { return it.eval(this); }
	public Outcome eval(Paper p) { return WIN; }
	public Outcome eval(Scissors s) { return LOSE; }
	public Outcome eval(Rock r) { return DRAW; }
}

用枚举型多路分发

用枚举实现多路分发,完全是另外一个思路:“写死”。下面是书里给的例子:

public enum RoShamBo2 implements Competitor<RoShamBo2> {
	PAPER(DRAW, LOSE, WIN),
	SCISSORS(WIN, DRAW, LOSE),
	ROCK(LOSE, WIN, DRAW);
	private Outcome vPAPER, vSCISSORS, vROCK;
	RoShamBo2(Outcome paper,Outcome scissors,Outcome rock) {
		this.vPAPER = paper;
		this.vSCISSORS = scissors;
		this.vROCK = rock;
	}

	public Outcome compete(RoShamBo2 it) {
		switch(it) {
			default:
			case PAPER: return vPAPER;
			case SCISSORS: return vSCISSORS;
			case ROCK: return vROCK;
		}
	}
}

“原理”也很简单,用枚举把石头,剪刀,布之间的胜负关系全部写死。实际玩游戏的时候,对参数进行switch。

表驱动编码

上面提到的这种“写死”的方法,看上去和呆萌,但在实际工程中很常用。《代码大全》中也有专门的一节描述这个模式,叫做“表驱动(table-driven code)”。

书上也有一个专门的例子。还是石头剪刀布的例子,彻底用一个二维数组把对决结果全都写死:

enum RoShamBo6 implements Competitor<RoShamBo6> {
	PAPER, SCISSORS, ROCK;
	private static Outcome[][] table = {
		{ DRAW, LOSE, WIN }, // PAPER
		{ WIN, DRAW, LOSE }, // SCISSORS
		{ LOSE, WIN, DRAW }, // ROCK
	};
	public Outcome compete(RoShamBo6 other) {
		return table[this.ordinal()][other.ordinal()];
	}
}

练习

本章练习常用工具包

枚举随机器

Enums.Random()

package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Enums{
    private static Random rand=new Random();

    public static <T extends Enum<T>> T random(Class<T> c ){
        return random(c.getEnumConstants());
    }
    public static <T> T random(T[] values){
        return values[rand.nextInt(values.length)];
    }
    public static void main(String[] args){
        //System.out.println(random(CartoonCharacter2.class));
    }
}

Exercise 1

除了完成题目,还练习了最基本的Enum型的创建,构造。

Signal.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Signal {
    GREEN("You can pass!"),
    YELLOW("Red light coming soon!"),
    RED("You must stop!");

    private String description;
    private Signal(String s){
        this.description=s;
    }
    public String getDescription(){return description;}

    public String toString(){
        String id=name();
        return id;
    }
}
TrafficLight.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;
import static com.ciaoshen.thinkinjava.chapter19.Signal.*;

public class TrafficLight{
    private static Signal color=RED;

    public static void change(){
        switch(color){
            case RED:
                color=GREEN;
                break;
            case YELLOW:
                color=RED;
                break;
            case GREEN:
                color=YELLOW;
                break;
        }
    }
    public static void look(){System.out.println(color);}

    public static void main(String[] args){
        for(Signal s:values()){
            System.out.println(s+": "+s.getDescription());
        }
        for(int i=0;i<10;i++){
            change();
            look();
        }
    }
}
Exercise1.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;
import static com.ciaoshen.thinkinjava.chapter19.Signal.*;
import static com.ciaoshen.thinkinjava.chapter19.TrafficLight.*;

public class Exercise1{
    public static void main(String[] args){
        for(Signal s:values()){
            System.out.println(s+": "+s.getDescription());
        }
        for(int i=0;i<10;i++){
            change();
            look();
        }
    }
}

Exercise 2

用next()不用创建实例,用起来更方便了。缺点是面向Generator接口的方法要受到影响。

package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

enum CartoonCharacter{
    SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;

    private static Random rand=new Random();

    public static CartoonCharacter next(){
        return values()[rand.nextInt(values().length)];
    }
}

public class Exercise2{
    public static void main(String[] args){
        for(int i=0;i<10;i++){
            System.out.println(CartoonCharacter.next());
        }
    }
}

Exercise 3

Food.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public interface Food {
    enum Appetizer implements Food {
        SALAD, SOUP, SPRING_ROLLS;
    }
    enum MainCourse implements Food {
        LASAGNE, BURRITO, PAD_THAI,
        LENTILS, HUMMOUS, VINDALOO;
    }
    enum Dessert implements Food {
        TIRAMISU, GELATO, BLACK_FOREST_CAKE,
        FRUIT, CREME_CARAMEL;
    }
    enum Coffee implements Food {
        BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
        LATTE, CAPPUCCINO, TEA, HERB_TEA;
    }
    enum Drink implements Food {
        COKECOLA, APPLE_JUICE, ORINGE_JUICE;
    }
}
Course.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Course {
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class),
    DRINK(Food.Drink.class);

    private Food[] values;
    private Course(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }
    public Food randomSelection() {
        return Enums.random(values);
    }
}
Exercise3.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise3 {
    public static void main(String[] args) {
        for(int i = 0; i < 5; i++) {
            for(Course course : Course.values()) {
                Food food = course.randomSelection();
                System.out.println(food);
            }
            System.out.println("---");
        }
    }
}

Exercise 4

Meal2.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Meal2 {
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class),
    DRINK(Food.Drink.class);

    private Food[] values;
    private Meal2(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }
    public static interface Food {
        enum Appetizer implements Food {
            SALAD, SOUP, SPRING_ROLLS;
        }
        enum MainCourse implements Food {
            LASAGNE, BURRITO, PAD_THAI,
            LENTILS, HUMMOUS, VINDALOO;
        }
        enum Dessert implements Food {
            TIRAMISU, GELATO, BLACK_FOREST_CAKE,
            FRUIT, CREME_CARAMEL;
        }
        enum Coffee implements Food {
            BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
            LATTE, CAPPUCCINO, TEA, HERB_TEA;
        }
        enum Drink implements Food {
            COKECOLA, APPLE_JUICE, ORINGE_JUICE;
        }
    }

    public Food randomSelection() {
        return Enums.random(values);
    }
}
Exercise4.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise4 {
    public static void main(String[] args) {
        for(int i = 0; i < 5; i++) {
            for(Meal2 meal : Meal2.values()) {
                Meal2.Food food = meal.randomSelection();
                System.out.println(food);
            }
            System.out.println("---");
        }
    }
}

Exercise 5

package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Exercise5 {

    VOWEL("aeiou".split("")),
    SOMETIMES_A_VOWEL("y","w"),
    CONSONANT("bcdfghjklmnpqrstvxz".split(""));

    private String[] members;
    private Exercise5(String... letters){
        members=letters;
    }
    public String[] getMembers(){return members;}

    public static void main(String[] args) {
        Random rand = new Random();
        List<String> vo=Arrays.asList(VOWEL.getMembers());
        List<String> some=Arrays.asList(SOMETIMES_A_VOWEL.getMembers());
        List<String> con=Arrays.asList(CONSONANT.getMembers());
        for(int i = 0; i < 100; i++) {
            int ic=rand.nextInt(26) + 'a';
            String sc = new String(new char[]{(char)ic});
            System.out.println(sc + ", " + ic + ": "+(vo.contains(sc)? "Vowel":"")+(some.contains(sc)? "Sometime a Vowel":"")+(con.contains(sc)? "Consonants":""));
        }
    }
}

Exercise 6

Letters.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Letters {

    VOWEL(Letter.Vowel.class),
    SOMETIMES_A_VOWEL(Letter.SometimesAVowel.class),
    CONSONANT(Letter.Consonant.class);

    private Letter[] values;
    private Letters(Class<? extends Letter> c){
        values=c.getEnumConstants();
    }
    public Letter[] getValues(){
        return values;
    }

    public interface Letter{
        public enum Vowel implements Letter{
            A('a'),E('e'),I('i'),O('o'),U('u');
            private char name;
            private Vowel(char n){name=n;}
            public String toString(){return new String(new char[]{name});}
        }
        public enum SometimesAVowel implements Letter{
            Y('y'),W('w');
            private char name;
            private SometimesAVowel(char n){name=n;}
            public String toString(){return new String(new char[]{name});}
        }
        public enum Consonant implements Letter{
            B('b'),C('c'),D('d'),F('f'),G('g'),H('h'),J('j'),K('k'),L('l'),M('m'),N('n'),P('p'),Q('q'),R('r'),S('s'),T('t'),V('v'),X('x'),Z('z');
            private char name;
            private Consonant(char n){name=n;}
            public String toString(){return new String(new char[]{name});}
        }
    }
}
Exercise6.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise6 implements Letters.Letter{
    public static void main(String[] args){
        Random rand=new Random();
        for(int j=0;j<10;j++){
            StringBuilder sb=new StringBuilder();
            for(int i=0;i<7;i++){
                Letters ls=Enums.random(Letters.class);
                Letter l=Enums.random(ls.getValues());
                sb.append(l.toString());
            }
            System.out.println(sb);
        }
    }
}

Exercise 7

以下代码为J2SE的numSet源码。

package java.util;

import sun.misc.SharedSecrets;

/**
 * A specialized {@link Set} implementation for use with enum types.  All of
 * the elements in an enum set must come from a single enum type that is
 * specified, explicitly or implicitly, when the set is created.  Enum sets
 * are represented internally as bit vectors.  This representation is
 * extremely compact and efficient. The space and time performance of this
 * class should be good enough to allow its use as a high-quality, typesafe
 * alternative to traditional <tt>int</tt>-based "bit flags."  Even bulk
 * operations (such as <tt>containsAll</tt> and <tt>retainAll</tt>) should
 * run very quickly if their argument is also an enum set.
 *
 * <p>The iterator returned by the <tt>iterator</tt> method traverses the
 * elements in their <i>natural order</i> (the order in which the enum
 * constants are declared).  The returned iterator is <i>weakly
 * consistent</i>: it will never throw {@link ConcurrentModificationException}
 * and it may or may not show the effects of any modifications to the set that
 * occur while the iteration is in progress.
 *
 * <p>Null elements are not permitted.  Attempts to insert a null element
 * will throw {@link NullPointerException}.  Attempts to test for the
 * presence of a null element or to remove one will, however, function
 * properly.
 *
 * <P>Like most collection implementations, <tt>EnumSet</tt> is not
 * synchronized.  If multiple threads access an enum set concurrently, and at
 * least one of the threads modifies the set, it should be synchronized
 * externally.  This is typically accomplished by synchronizing on some
 * object that naturally encapsulates the enum set.  If no such object exists,
 * the set should be "wrapped" using the {@link Collections#synchronizedSet}
 * method.  This is best done at creation time, to prevent accidental
 * unsynchronized access:
 *
 * <pre>
 * Set&lt;MyEnum&gt; s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
 * </pre>
 *
 * <p>Implementation note: All basic operations execute in constant time.
 * They are likely (though not guaranteed) to be much faster than their
 * {@link HashSet} counterparts.  Even bulk operations execute in
 * constant time if their argument is also an enum set.
 *
 * <p>This class is a member of the
 * <a href="{@docRoot}/../technotes/guides/collections/index.html">
 * Java Collections Framework</a>.
 *
 * @author Josh Bloch
 * @since 1.5
 * @see EnumMap
 * @serial exclude
 */
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
    implements Cloneable, java.io.Serializable
{
    /**
     * The class of all the elements of this set.
     */
    final Class<E> elementType;

    /**
     * All of the values comprising T.  (Cached for performance.)
     */
    final Enum[] universe;

    private static Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0];

    EnumSet(Class<E>elementType, Enum[] universe) {
        this.elementType = elementType;
        this.universe    = universe;
    }

    /**
     * Creates an empty enum set with the specified element type.
     *
     * @param elementType the class object of the element type for this enum
     *     set
     * @throws NullPointerException if <tt>elementType</tt> is null
     */
    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

    /**
     * Creates an enum set containing all of the elements in the specified
     * element type.
     *
     * @param elementType the class object of the element type for this enum
     *     set
     * @throws NullPointerException if <tt>elementType</tt> is null
     */
    public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
        EnumSet<E> result = noneOf(elementType);
        result.addAll();
        return result;
    }

    /**
     * Adds all of the elements from the appropriate enum type to this enum
     * set, which is empty prior to the call.
     */
    abstract void addAll();

    /**
     * Creates an enum set with the same element type as the specified enum
     * set, initially containing the same elements (if any).
     *
     * @param s the enum set from which to initialize this enum set
     * @throws NullPointerException if <tt>s</tt> is null
     */
    public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) {
        return s.clone();
    }

    /**
     * Creates an enum set initialized from the specified collection.  If
     * the specified collection is an <tt>EnumSet</tt> instance, this static
     * factory method behaves identically to {@link #copyOf(EnumSet)}.
     * Otherwise, the specified collection must contain at least one element
     * (in order to determine the new enum set's element type).
     *
     * @param c the collection from which to initialize this enum set
     * @throws IllegalArgumentException if <tt>c</tt> is not an
     *     <tt>EnumSet</tt> instance and contains no elements
     * @throws NullPointerException if <tt>c</tt> is null
     */
    public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) {
        if (c instanceof EnumSet) {
            return ((EnumSet<E>)c).clone();
        } else {
            if (c.isEmpty())
                throw new IllegalArgumentException("Collection is empty");
            Iterator<E> i = c.iterator();
            E first = i.next();
            EnumSet<E> result = EnumSet.of(first);
            while (i.hasNext())
                result.add(i.next());
            return result;
        }
    }

    /**
     * Creates an enum set with the same element type as the specified enum
     * set, initially containing all the elements of this type that are
     * <i>not</i> contained in the specified set.
     *
     * @param s the enum set from whose complement to initialize this enum set
     * @throws NullPointerException if <tt>s</tt> is null
     */
    public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) {
        EnumSet<E> result = copyOf(s);
        result.complement();
        return result;
    }

    /**
     * Creates an enum set initially containing the specified element.
     *
     * Overloadings of this method exist to initialize an enum set with
     * one through five elements.  A sixth overloading is provided that
     * uses the varargs feature.  This overloading may be used to create
     * an enum set initially containing an arbitrary number of elements, but
     * is likely to run slower than the overloadings that do not use varargs.
     *
     * @param e the element that this set is to contain initially
     * @throws NullPointerException if <tt>e</tt> is null
     * @return an enum set initially containing the specified element
     */
    public static <E extends Enum<E>> EnumSet<E> of(E e) {
        EnumSet<E> result = noneOf(e.getDeclaringClass());
        result.add(e);
        return result;
    }

    /**
     * Creates an enum set initially containing the specified elements.
     *
     * Overloadings of this method exist to initialize an enum set with
     * one through five elements.  A sixth overloading is provided that
     * uses the varargs feature.  This overloading may be used to create
     * an enum set initially containing an arbitrary number of elements, but
     * is likely to run slower than the overloadings that do not use varargs.
     *
     * @param e1 an element that this set is to contain initially
     * @param e2 another element that this set is to contain initially
     * @throws NullPointerException if any parameters are null
     * @return an enum set initially containing the specified elements
     */
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        return result;
    }

    /**
     * Creates an enum set initially containing the specified elements.
     *
     * Overloadings of this method exist to initialize an enum set with
     * one through five elements.  A sixth overloading is provided that
     * uses the varargs feature.  This overloading may be used to create
     * an enum set initially containing an arbitrary number of elements, but
     * is likely to run slower than the overloadings that do not use varargs.
     *
     * @param e1 an element that this set is to contain initially
     * @param e2 another element that this set is to contain initially
     * @param e3 another element that this set is to contain initially
     * @throws NullPointerException if any parameters are null
     * @return an enum set initially containing the specified elements
     */
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        result.add(e3);
        return result;
    }

    /**
     * Creates an enum set initially containing the specified elements.
     *
     * Overloadings of this method exist to initialize an enum set with
     * one through five elements.  A sixth overloading is provided that
     * uses the varargs feature.  This overloading may be used to create
     * an enum set initially containing an arbitrary number of elements, but
     * is likely to run slower than the overloadings that do not use varargs.
     *
     * @param e1 an element that this set is to contain initially
     * @param e2 another element that this set is to contain initially
     * @param e3 another element that this set is to contain initially
     * @param e4 another element that this set is to contain initially
     * @throws NullPointerException if any parameters are null
     * @return an enum set initially containing the specified elements
     */
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        result.add(e3);
        result.add(e4);
        return result;
    }

    /**
     * Creates an enum set initially containing the specified elements.
     *
     * Overloadings of this method exist to initialize an enum set with
     * one through five elements.  A sixth overloading is provided that
     * uses the varargs feature.  This overloading may be used to create
     * an enum set initially containing an arbitrary number of elements, but
     * is likely to run slower than the overloadings that do not use varargs.
     *
     * @param e1 an element that this set is to contain initially
     * @param e2 another element that this set is to contain initially
     * @param e3 another element that this set is to contain initially
     * @param e4 another element that this set is to contain initially
     * @param e5 another element that this set is to contain initially
     * @throws NullPointerException if any parameters are null
     * @return an enum set initially containing the specified elements
     */
    public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4,
                                                    E e5)
    {
        EnumSet<E> result = noneOf(e1.getDeclaringClass());
        result.add(e1);
        result.add(e2);
        result.add(e3);
        result.add(e4);
        result.add(e5);
        return result;
    }

    /**
     * Creates an enum set initially containing the specified elements.
     * This factory, whose parameter list uses the varargs feature, may
     * be used to create an enum set initially containing an arbitrary
     * number of elements, but it is likely to run slower than the overloadings
     * that do not use varargs.
     *
     * @param first an element that the set is to contain initially
     * @param rest the remaining elements the set is to contain initially
     * @throws NullPointerException if any of the specified elements are null,
     *     or if <tt>rest</tt> is null
     * @return an enum set initially containing the specified elements
     */
    @SafeVarargs
    public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
        EnumSet<E> result = noneOf(first.getDeclaringClass());
        result.add(first);
        for (E e : rest)
            result.add(e);
        return result;
    }

    /**
     * Creates an enum set initially containing all of the elements in the
     * range defined by the two specified endpoints.  The returned set will
     * contain the endpoints themselves, which may be identical but must not
     * be out of order.
     *
     * @param from the first element in the range
     * @param to the last element in the range
     * @throws NullPointerException if {@code from} or {@code to} are null
     * @throws IllegalArgumentException if {@code from.compareTo(to) > 0}
     * @return an enum set initially containing all of the elements in the
     *         range defined by the two specified endpoints
     */
    public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
        if (from.compareTo(to) > 0)
            throw new IllegalArgumentException(from + " > " + to);
        EnumSet<E> result = noneOf(from.getDeclaringClass());
        result.addRange(from, to);
        return result;
    }

    /**
     * Adds the specified range to this enum set, which is empty prior
     * to the call.
     */
    abstract void addRange(E from, E to);

    /**
     * Returns a copy of this set.
     *
     * @return a copy of this set
     */
    public EnumSet<E> clone() {
        try {
            return (EnumSet<E>) super.clone();
        } catch(CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }

    /**
     * Complements the contents of this enum set.
     */
    abstract void complement();

    /**
     * Throws an exception if e is not of the correct type for this enum set.
     */
    final void typeCheck(E e) {
        Class eClass = e.getClass();
        if (eClass != elementType && eClass.getSuperclass() != elementType)
            throw new ClassCastException(eClass + " != " + elementType);
    }

    /**
     * Returns all of the values comprising E.
     * The result is uncloned, cached, and shared by all callers.
     */
    private static <E extends Enum<E>> E[] getUniverse(Class<E> elementType) {
        return SharedSecrets.getJavaLangAccess()
                                        .getEnumConstantsShared(elementType);
    }

    /**
     * This class is used to serialize all EnumSet instances, regardless of
     * implementation type.  It captures their "logical contents" and they
     * are reconstructed using public static factories.  This is necessary
     * to ensure that the existence of a particular implementation type is
     * an implementation detail.
     *
     * @serial include
     */
    private static class SerializationProxy <E extends Enum<E>>
        implements java.io.Serializable
    {
        /**
         * The element type of this enum set.
         *
         * @serial
         */
        private final Class<E> elementType;

        /**
         * The elements contained in this enum set.
         *
         * @serial
         */
        private final Enum[] elements;

        SerializationProxy(EnumSet<E> set) {
            elementType = set.elementType;
            elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY);
        }

        private Object readResolve() {
            EnumSet<E> result = EnumSet.noneOf(elementType);
            for (Enum e : elements)
                result.add((E)e);
            return result;
        }

        private static final long serialVersionUID = 362491234563181265L;
    }

    Object writeReplace() {
        return new SerializationProxy<>(this);
    }

    // readObject method for the serialization proxy pattern
    // See Effective Java, Second Ed., Item 78.
    private void readObject(java.io.ObjectInputStream stream)
        throws java.io.InvalidObjectException {
        throw new java.io.InvalidObjectException("Proxy required");
    }
}

首先,EnumSet是抽象类。和大多数类库自带Set一样,它继承自AbstractSet,获得了AbstractSet和AbstractMap的部分功能。

然后,至少我见到了有两个类是继承EnumSet的:一个RegularEnumSet,一个JumboEnumSet。但之所以我们在代码中很少见到直接调用这两个类的构造函数,是因为EnumSet的构造函数承担了根据枚举规模选择调用它们其中的哪一个构造函数。通过noneOf()函数中的如下部分代码,基本可以判断:RegularEnumSet用在枚举规模小于64的情况下。而JamboEnumSet用在大于64的情况下。要做这样的拆分,估计还是出于效率的考虑。毕竟可扩展大小的开销不小。

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

在RegularEnumSet中维护的主力数据结构确实如书中所说是一个long型:

private long elements = 0L;

然后看它的add()函数:明显是把long型64 bit中,某个枚举实例的次序属性(ordinal)所对应的位置设为1。用1 bit表示这个枚举实例的存在。所以也确实像书里说的,long型中的每一位,映射到一个具体的枚举实例。

    public boolean add(E e) {
        typeCheck(e);

        long oldElements = elements;
        elements |= (1L << ((Enum)e).ordinal());
        return elements != oldElements;
    }

至于JumboEnumSet,其中维护的主力容器是一个long型数组。看看它的构造器:

class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
	private static final long serialVersionUID = 334349849919042784L;

	private long elements[];

    private int size = 0;

    JumboEnumSet(Class<E>elementType, Enum[] universe) {
        super(elementType, universe);
        elements = new long[(universe.length + 63) >>> 6];
    }
	//... ...

相当于枚举规模对64取模获得的数组长度。

Exercise 8

Mail.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Mail {
    // The NO’s lower the probability of random selection:
    enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}
    enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}
    enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
    enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}
    enum Forward {YES,NO}
    enum ForwardAddress {INCORRECT,OK1,OK2,OK3,OK4,OK5}
    enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}

    GeneralDelivery generalDelivery;
    Scannability scannability;
    Readability readability;
    Address address;
    Forward forward;
    ForwardAddress forwardAddress;
    ReturnAddress returnAddress;

    static long counter = 0;
    long id = counter++;
    public String toString() { return "Mail " + id; }
    public String details() {
        return toString() +
        ", General Delivery: " + generalDelivery +
        ", Address Scanability: " + scannability +
        ", Address Readability: " + readability +
        ", Address Address: " + address +
        ", Forward: " + forward +
        ", Forward Address: " + forwardAddress +
        ", Return address: " + returnAddress;
    }
    // Generate test Mail:
    public static Mail randomMail() {
        Mail m = new Mail();
        m.generalDelivery= Enums.random(GeneralDelivery.class);
        m.scannability = Enums.random(Scannability.class);
        m.readability = Enums.random(Readability.class);
        m.address = Enums.random(Address.class);
        m.forward = Enums.random(Forward.class);
        m.forwardAddress = Enums.random(ForwardAddress.class);
        m.returnAddress = Enums.random(ReturnAddress.class);
        return m;
    }
    public static Iterable<Mail> generator(final int count) {
        return new Iterable<Mail>() {
            int n = count;
            public Iterator<Mail> iterator() {
                return new Iterator<Mail>() {
                    public boolean hasNext() { return n-- > 0; }
                    public Mail next() { return randomMail(); }
                    public void remove() { // Not implemented
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }
}
PostOffice.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class PostOffice {
    enum MailHandler {
        GENERAL_DELIVERY {
            boolean handle(Mail m) {
                switch(m.generalDelivery) {
                    case YES:
                        System.out.println("Using general delivery for " + m);
                        return true;
                    default: return false;
                }
            }
        },
        MACHINE_SCAN {
            boolean handle(Mail m) {
                switch(m.scannability) {
                    case UNSCANNABLE: return false;
                    default:
                        switch(m.address) {
                            case INCORRECT: return false;
                            default:
                                System.out.println("Delivering "+ m + " automatically");
                                return true;
                        }
                }
            }
        },
        VISUAL_INSPECTION {
            boolean handle(Mail m) {
                switch(m.readability) {
                    case ILLEGIBLE: return false;
                    default:
                        switch(m.address) {
                            case INCORRECT: return false;
                            default:
                                System.out.println("Delivering " + m + " normally");
                                return true;
                        }
                }
            }
        },
        FORWARD_STEP {
            boolean handle(Mail m) {
                switch(m.forward){
                    case NO:
                        return false;
                    default:
                        switch(m.forwardAddress){
                            case INCORRECT: return false;
                            default:
                                System.out.println("Forward "+ m +" normally");
                                return true;
                        }
                }
            }
        },
        RETURN_TO_SENDER {
            boolean handle(Mail m) {
                switch(m.returnAddress) {
                    case MISSING: return false;
                    default:
                        System.out.println("Returning " + m + " to sender");
                        return true;
                }
            }
        };
        abstract boolean handle(Mail m);
    }
    static void handle(Mail m) {
        for(MailHandler handler : MailHandler.values())
            if(handler.handle(m))
                return;
        System.out.println(m + " is a dead letter");
    }
}
Exercise8.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise8 {
    public static void main(String[] args) {
        for(Mail mail : Mail.generator(10)) {
            System.out.println(mail.details());
            PostOffice.handle(mail);
            System.out.println("*****");
        }
    }
}

Exercise 9

Mail.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Mail {
    // The NO’s lower the probability of random selection:
    enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}
    enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}
    enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
    enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}
    enum Forward {YES,NO}
    enum ForwardAddress {INCORRECT,OK1,OK2,OK3,OK4,OK5}
    enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}

    GeneralDelivery generalDelivery;
    Scannability scannability;
    Readability readability;
    Address address;
    Forward forward;
    ForwardAddress forwardAddress;
    ReturnAddress returnAddress;

    static long counter = 0;
    long id = counter++;
    public String toString() { return "Mail " + id; }
    public String details() {
        return toString() +
        ", General Delivery: " + generalDelivery +
        ", Address Scanability: " + scannability +
        ", Address Readability: " + readability +
        ", Address Address: " + address +
        ", Forward: " + forward +
        ", Forward Address: " + forwardAddress +
        ", Return address: " + returnAddress;
    }
    // Generate test Mail:
    public static Mail randomMail() {
        Mail m = new Mail();
        m.generalDelivery= Enums.random(GeneralDelivery.class);
        m.scannability = Enums.random(Scannability.class);
        m.readability = Enums.random(Readability.class);
        m.address = Enums.random(Address.class);
        m.forward = Enums.random(Forward.class);
        m.forwardAddress = Enums.random(ForwardAddress.class);
        m.returnAddress = Enums.random(ReturnAddress.class);
        return m;
    }
    public static Iterable<Mail> generator(final int count) {
        return new Iterable<Mail>() {
            int n = count;
            public Iterator<Mail> iterator() {
                return new Iterator<Mail>() {
                    public boolean hasNext() { return n-- > 0; }
                    public Mail next() { return randomMail(); }
                    public void remove() { // Not implemented
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }
}
PostOffice9.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class PostOffice9 {
    enum MailHandler { GENERAL_DELIVERY, MACHINE_SCAN, VISUAL_INSPECTION, FORWARD_STEP, RETURN_TO_SENDER }

    public static interface Handler{public boolean handle(Mail m);}

    private static EnumMap<MailHandler, PostOffice9.Handler> em=new EnumMap<MailHandler,PostOffice9.Handler>(MailHandler.class);

    static{
        em.put(MailHandler.GENERAL_DELIVERY,new PostOffice9.Handler(){
            public boolean handle(Mail m) {
                switch(m.generalDelivery) {
                    case YES:
                        System.out.println("Using general delivery for " + m);
                        return true;
                    default: return false;
                }
            }
        });
        em.put(MailHandler.MACHINE_SCAN, new PostOffice9.Handler(){
            public boolean handle(Mail m) {
                switch(m.scannability) {
                    case UNSCANNABLE: return false;
                    default:
                        switch(m.address) {
                            case INCORRECT: return false;
                            default:
                                System.out.println("Delivering "+ m + " automatically");
                                return true;
                        }
                }
            }
        });
        em.put(MailHandler.VISUAL_INSPECTION, new PostOffice9.Handler(){
            public boolean handle(Mail m) {
                switch(m.readability) {
                    case ILLEGIBLE: return false;
                    default:
                        switch(m.address) {
                            case INCORRECT: return false;
                            default:
                                System.out.println("Delivering " + m + " normally");
                                return true;
                        }
                }
            }
        });
        em.put(MailHandler.FORWARD_STEP, new PostOffice9.Handler(){
            public boolean handle(Mail m) {
                switch(m.forward){
                    case NO:
                        return false;
                    default:
                        switch(m.forwardAddress){
                            case INCORRECT: return false;
                            default:
                                System.out.println("Forward "+ m +" normally");
                                return true;
                        }
                }
            }
        });
        em.put(MailHandler.RETURN_TO_SENDER, new PostOffice9.Handler(){
            public boolean handle(Mail m) {
                switch(m.returnAddress) {
                    case MISSING: return false;
                    default:
                        System.out.println("Returning " + m + " to sender");
                        return true;
                }
            }
        });
    }


    public static void handle(Mail m) {
        for(Map.Entry<MailHandler,PostOffice9.Handler> entry: PostOffice9.em.entrySet()){
            if(entry.getValue()!=null){
               if(entry.getValue().handle(m)){
                   return;
               }
            }
        }
        System.out.println(m + " is a dead letter");
    }

    public static void showHandleMap(){
        System.out.println(em);
    }

    public static void main(String[] args){

    }
}
Exercise9.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise9 {
    public static void main(String[] args) {
        for(Mail mail : Mail.generator(10)) {
            System.out.println(mail.details());
            PostOffice9.handle(mail);
            System.out.println("*****");
        }
    }
}

Exercise 10

思路很简单,利用“命令模式”,把状态机的“状态”和“操作”分离。“状态”继续保持静态,放在State枚举型里。但具体的next()操作作为value和“状态”一起存入EnumMap里。而EnumMap作为VendingMachine的一个成员字段被维护。

Input.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Input {

    /**
     *  枚举成员
     */
    NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100), TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),
    ABORT_TRANSACTION {
        public int amount() { // Disallow
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP { // This must be the last instance.
        public int amount() { // Disallow
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };

    /**
     *  其他成员
     */
    int value; // In cents
    Input(int value) { this.value = value; }
    Input() {}
    int amount() { return value; }; // In cents
    static Random rand = new Random();
    public static Input randomSelection() {
        // Don’t include STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}
Category.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;
import static com.ciaoshen.thinkinjava.chapter19.Input.*;

public enum Category {
    MONEY(NICKEL, DIME, QUARTER, DOLLAR),
    ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),
    QUIT_TRANSACTION(ABORT_TRANSACTION),
    SHUT_DOWN(STOP);

    private Input[] values;
    Category(Input... types) { values = types; }
    private static EnumMap<Input,Category> categories = new EnumMap<Input,Category>(Input.class);

    static {
        for(Category c : Category.class.getEnumConstants()) {
            for(Input type : c.values){
                categories.put(type, c);
            }
        }
    }

    public static Category categorize(Input input) {
        return categories.get(input);
    }
}
RandomInputGenerator.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

// For a basic sanity check:
public class RandomInputGenerator implements Generator<Input> {
    public Input next() { return Input.randomSelection(); }
}
VendingMachine.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class VendingMachine {
    enum StateDuration { TRANSIENT } // Tagging enum
    enum State {
        RESTING, ADDING_MONEY, DISPENSING(StateDuration.TRANSIENT), GIVING_CHANGE(StateDuration.TRANSIENT),
        TERMINAL;

        private boolean isTransient = false;
        State() {}
        State(StateDuration trans) { isTransient = true; }

    }

    private State state = State.RESTING;
    private int amount = 0;
    private Input selection = null;
    private EnumMap<State,StateMachine> em=new EnumMap<State,StateMachine>(State.class);

    private class StateMachine {
        void next(Input input) {
            throw new RuntimeException("Only call " + "next(Input input) for non-transient states");
        }
        void next() {
            throw new RuntimeException("Only call next() for " + "StateDuration.TRANSIENT states");
        }
        void output() { System.out.println(amount); }
    }

    public VendingMachine() {
        em.put(State.RESTING,new StateMachine(){
            void next(Input input) {
                switch(Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = State.ADDING_MONEY;
                        break;
                    case SHUT_DOWN:
                        state = State.TERMINAL;
                    default:
                }
            }
        });
        em.put(State.ADDING_MONEY,new StateMachine(){
            void next(Input input) {
                switch(Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        break;
                    case ITEM_SELECTION:
                        selection = input;
                        if(amount < selection.amount())
                            System.out.println("Insufficient money for " + selection);
                        else state = State.DISPENSING;
                        break;
                    case QUIT_TRANSACTION:
                        state = State.GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = State.TERMINAL;
                    default:
                }
            }
        });
        em.put(State.DISPENSING,new StateMachine(){
            void next() {
                System.out.println("here is your " + selection);
                amount -= selection.amount();
                state = State.GIVING_CHANGE;
            }
        });
        em.put(State.GIVING_CHANGE,new StateMachine(){
            void next() {
                if(amount > 0) {
                    System.out.println("Your change: " + amount);
                    amount = 0;
                }
                state = State.TERMINAL;
            }
        });
        em.put(State.TERMINAL,new StateMachine(){
            void output() { System.out.println("Halted"); }
        });
    }

    public void reset(){
        state = State.RESTING;
        amount = 0;
        selection = null;
    }

    public void run(Generator<Input> gen) {
        while(state != State.TERMINAL) {
            em.get(state).next(gen.next());
            while(state.isTransient){
                em.get(state).next();
            }
            em.get(state).output();
        }
    }

    public static void main(String[] args) {
        VendingMachine vm=new VendingMachine();
        vm.run(new RandomInputGenerator());
    }
}
Exercise10.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise10 {
    public static void main(String[] args) {
        VendingMachine vm1=new VendingMachine();
        Generator<Input> gen=new RandomInputGenerator();
        vm1.run(gen);
        vm1.reset();
        vm1.run(gen);

        VendingMachine vm2=new VendingMachine();
        vm2.run(gen);
    }
}

Exercise 11

思路也很简单,把原先的商品设计成Product类,允许从外部文件读入商品条目。售货机初始化时完成载入商品条目。再把原先ITEM_SELECTION枚举型Input改成一个标记。当状态机收到ITEM_SELECTION型状态时,next()方法变为从商品条目列表中随机读取一个商品。

Input11.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public enum Input11 {

    /**
     *  枚举成员
     */
    NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100), PRODUCT(0),
    ABORT_TRANSACTION {
        public int amount() { // Disallow
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP { // This must be the last instance.
        public int amount() { // Disallow
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };

    /**
     *  其他成员
     */
    int value; // In cents
    Input11(int value) { this.value = value; }
    Input11() {}
    int amount() { return value; }; // In cents
    static Random rand = new Random();
    public static Input11 randomSelection() {
        // Don’t include STOP:
        return values()[rand.nextInt(values().length - 1)];
    }
}
RandomInputGenerator11.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

// For a basic sanity check:
public class RandomInputGenerator11 implements Generator<Input11> {
    public Input11 next() { return Input11.randomSelection(); }
}
Category11.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;
import static com.ciaoshen.thinkinjava.chapter19.Input11.*;

public enum Category11 {
    MONEY(NICKEL, DIME, QUARTER, DOLLAR),
    ITEM_SELECTION(PRODUCT),
    QUIT_TRANSACTION(ABORT_TRANSACTION),
    SHUT_DOWN(STOP);

    private static EnumMap<Input11,Category11> categories = new EnumMap<Input11,Category11>(Input11.class);

    static {
        for(Category11 c : Category11.class.getEnumConstants()) {
            for(Input11 type : c.values){
                categories.put(type, c);
            }
        }
    }

    public static Category11 categorize(Input11 input) {
        return categories.get(input);
    }

    private Input11[] values;
    Category11(Input11... types) { values = types; }

    public static void main(String[] args){
        System.out.println(Category11.MONEY);
        System.out.println(Category11.categories);
    }
}
VendingMachine11.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class VendingMachine11 {
    enum StateDuration { TRANSIENT } // Tagging enum
    enum State {
        RESTING, ADDING_MONEY, DISPENSING(StateDuration.TRANSIENT), GIVING_CHANGE(StateDuration.TRANSIENT),
        TERMINAL;

        private boolean isTransient = false;
        State() {}
        State(StateDuration trans) { isTransient = true; }

    }

    private State state = State.RESTING;
    private int amount = 0;
    private Product selection = null;
    private EnumMap<State,StateMachine> em=new EnumMap<State,StateMachine>(State.class);

    private class StateMachine {
        void next(Input11 input) {
            throw new RuntimeException("Only call " + "next(Input input) for non-transient states");
        }
        void next() {
            throw new RuntimeException("Only call next() for " + "StateDuration.TRANSIENT states");
        }
        void output() { System.out.println(amount); }
    }

    public VendingMachine11(String path) {
        Product.loadProducts(path);
        em.put(State.RESTING,new StateMachine(){
            void next(Input11 input) {
                switch(Category11.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = State.ADDING_MONEY;
                        break;
                    case SHUT_DOWN:
                        state = State.TERMINAL;
                    default:
                }
            }
        });
        em.put(State.ADDING_MONEY,new StateMachine(){
            void next(Input11 input) {
                switch(Category11.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        break;
                    case ITEM_SELECTION:
                        selection = Product.randomSelection();
                        if(amount < selection.amount()){
                            System.out.println("Insufficient money for " + selection);
                        }
                        else state = State.DISPENSING;
                        break;
                    case QUIT_TRANSACTION:
                        state = State.GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = State.TERMINAL;
                    default:
                }
            }
        });
        em.put(State.DISPENSING,new StateMachine(){
            void next() {
                System.out.println("here is your " + selection);
                amount -= selection.amount();
                state = State.GIVING_CHANGE;
            }
        });
        em.put(State.GIVING_CHANGE,new StateMachine(){
            void next() {
                if(amount > 0) {
                    System.out.println("Your change: " + amount);
                    amount = 0;
                }
                state = State.TERMINAL;
            }
        });
        em.put(State.TERMINAL,new StateMachine(){
            void output() { System.out.println("Halted"); }
        });
    }

    public void reset(){
        state = State.RESTING;
        amount = 0;
        selection = null;
    }

    public void run(Generator<Input11> gen) {
        while(state != State.TERMINAL) {
            em.get(state).next(gen.next());
            while(state.isTransient){
                em.get(state).next();
            }
            em.get(state).output();
        }
    }

    public static void main(String[] args) {
        VendingMachine11 vm=new VendingMachine11("/Users/Wei/java/com/ciaoshen/thinkinjava/chapter19/product.txt");
        vm.run(new RandomInputGenerator11());
    }
}
TextFile.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;
import java.io.*;

public class TextFile extends ArrayList<String> {
    // Read a file as a single string:
    public static String read(String fileName) {
        StringBuilder sb = new StringBuilder();
        try {
            BufferedReader in= new BufferedReader(new FileReader(new File(fileName).getAbsoluteFile()));
            try {
                String s;
                while((s = in.readLine()) != null) {
                    sb.append(s);
                    sb.append("\n");
                }
            } finally {
                in.close();
            }
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    public TextFile(String fileName, String splitter) {
        super(Arrays.asList(read(fileName).split(splitter)));
        // Regular expression split() often leaves an empty
        // String at the first position:
        if(get(0).equals("")) remove(0);
    }
    // Normally read by lines:
    public TextFile(String fileName) {
        this(fileName, "\n");
    }
}
product.txt

存放商品条目的文本文件。每行存放一种商品的信息。第一项:商品名,第二项:价格。中间用tab符号分隔。

TOOTTHPASTE	200
CHIPS	75
SODA	100
SOAP	50
Exercise11.java
package com.ciaoshen.thinkinjava.chapter19;
import java.util.*;

public class Exercise11 {
    public static void main(String[] args) {
        VendingMachine11 vm=new VendingMachine11("/Users/Wei/java/com/ciaoshen/thinkinjava/chapter19/product.txt");
        Generator<Input11> gen=new RandomInputGenerator11();
        vm.run(gen);
    }
}